package org.xx.armory.security;

import org.xx.armory.commons.NameObject;
import org.xx.armory.commons.NameObjectBag;

import java.io.Serializable;
import java.util.stream.StreamSupport;

import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notBlank;

/**
 * 身份认证令牌。
 *
 * <p>
 * 令牌可以以多种方式保存, 比如在{@literal cookie}, {@literal session}等等。
 * </p>
 *
 * @author Haart
 */
public class AuthenticationToken
        extends NameObjectBag
        implements Serializable {
    private static final long serialVersionUID = 1L;

    private final String userId;

    private final boolean guest;

    private final boolean persist;

    /**
     * 构造身份认证令牌。
     *
     * @param userId
     *         帐户ID, 表示唯一确认身份的ID。
     */
    public AuthenticationToken(
            String userId
    ) {
        this(userId, false);
    }

    /**
     * 构造身份认证令牌。
     *
     * @param userId
     *         帐户ID, 表示唯一确认身份的ID。
     * @param persist
     *         该令牌是否应当持久保存。
     */
    public AuthenticationToken(
            String userId,
            boolean persist
    ) {
        this.userId = trimToEmpty(userId);
        this.guest = isBlank(userId);
        this.persist = persist;
    }

    /**
     * 表示不代表任何身份的令牌。
     *
     * @return 不代表任何身份的令牌。
     */
    public static AuthenticationToken guest() {
        return new GuestAuthenticationToken();
    }

    /**
     * 获取用户ID。
     *
     * @return 用户ID。
     */
    public final String getUserId() {
        return this.userId;
    }

    /**
     * 判断是否持久有效令牌。
     *
     * @return 如果是持久有效令牌则返回 {@code true}, 否则返回{@code false}。
     */
    public final boolean isPersist() {
        return this.persist;
    }

    /**
     * 判断是否来宾令牌。
     *
     * @return 如果是来宾令牌则返回{@code true}, 否则返回{@code false}。
     */
    public final boolean isGuest() {
        return this.guest;
    }

    /**
     * 按照前缀清除数据。
     *
     * @param prefix
     *         指定的前缀。
     * @throws IllegalArgumentException
     *         如果参数{@code prefix}是{@code null}或者只包含空白字符。
     */
    public final void clearByPrefix(
            String prefix
    ) {
        final String prefix_ = notBlank(prefix, "prefix").trim();

        // 找到所有符合条件的键。
        final String[] keys = StreamSupport.stream(this.spliterator(), false)
                                           .map(NameObject::getName)
                                           .filter(name -> name.startsWith(prefix_))
                                           .toArray(String[]::new);

        // 然后逐个清除。
        for (final String key : keys) {
            remove(key);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int hashCode() {
        return this.userId == null ? 0 : this.userId.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof AuthenticationToken)) {
            return false;
        }
        AuthenticationToken other = (AuthenticationToken) obj;
        if (this.userId == null) {
            return other.userId == null;
        } else
            return this.userId.equals(other.userId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.getClass().getSimpleName() + "{user-id=" + this.userId + ", "
                + (this.persist ? "persist," : "")
                + "size=" + this.size() + "}";
    }
}
